// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Text;

using Microsoft.TeamFoundation.Migration.Toolkit;

namespace Microsoft.TeamFoundation.Migration.Toolkit.Wit
{
    /// <summary>
    /// Revision mapper class; stores information about mapped revisions of synchronized work items.
    /// </summary>
    class RevisionMapper
    {
        //$TODO_226583: remove all mappings below the waterline
        private SyncProcess m_sharedData;                   // Shared data object
        private int m_sessionId;                            // Session id
        private Pair<int> m_sourceId;                       // Source ids

        private Pair<int> m_ids;                            // Paired items (internal ids)
        private Pair<string> m_itemIds;                     // Paired items (public ids)
        private Pair<int> m_pairs;                          // Pairs

        private Pair<int> m_lastMigrated;                   // Last revision migrated from each side
        private Pair<int> m_baseline;                       // Baselines
        private Pair<Dictionary<int, int>> m_syncedRevs;    // Revisions, created by syncing from the opposite side
        
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="sharedData">Shared data object</param>
        /// <param name="leftItem">Source work item</param>
        public RevisionMapper(
            SyncProcess sharedData,
            IMigrationWorkItem leftItem)
        {
            m_sharedData = sharedData;
            m_sessionId = sharedData.Mapper.SessionId;
            m_sourceId = new Pair<int>(
                sharedData.Mapper.SourceId[sharedData.StoreTypes.Left],
                sharedData.Mapper.SourceId[sharedData.StoreTypes.Right]);

            m_ids = new Pair<int>(-1, -1);
            m_itemIds = new Pair<string>(leftItem.Watermark.Id, null);
            m_pairs = new Pair<int>(-1, -1);
            m_lastMigrated = new Pair<int>(-1, -1);
            m_baseline = new Pair<int>(-1, -1);
            m_syncedRevs = new Pair<Dictionary<int, int>>(
                new Dictionary<int, int>(), new Dictionary<int, int>());

            Pair<int> lastIncoming = new Pair<int>(-1, -1);

            // Load mappings
            using (MigrationSqlTransaction trn = (MigrationSqlTransaction)DataAccessManager.Current.StartTransaction())
            {
                using (SqlCommand cmd = trn.CreateCommand())
                {
                    cmd.CommandText = "prc_iiFindAddMappings";
                    cmd.CommandType = CommandType.StoredProcedure;

                    cmd.Parameters.Add("@SessionId", SqlDbType.Int).Value = m_sessionId;
                    cmd.Parameters.Add("@SourceId", SqlDbType.Int).Value = m_sourceId.Left;
                    cmd.Parameters.Add("@PublicId", SqlDbType.NVarChar, SqlData.WorkItemIdLength).Value = leftItem.Watermark.Id;

                    using (SqlDataReader reader = cmd.ExecuteReader())
                    {
                        // Process source item id
                        reader.Read();
                        m_ids.Left = (int)reader["SourceId"];

                        // Process pairs
                        reader.NextResult();
                        while (reader.Read())
                        {
                            int pairId = (int)reader["Id"];
                            int sourceId = (int)reader["SourceId"];

                            if (sourceId == m_ids.Left)
                            {
                                m_ids.Right = (int)reader["TargetId"];
                                m_itemIds.Right = (string)reader["TargetPublicId"];
                                m_pairs.Right = pairId;
                            }
                            else
                            {
                                m_pairs.Left = pairId;
                            }
                        }

                        // Load mapped revisions
                        reader.NextResult();

                        while (reader.Read())
                        {
                            int pairId = (int)reader["PairId"];
                            int sourceRev = (int)reader["SourceRev"];
                            int targetRev = (int)reader["TargetRev"];
                            Side sourceSide = pairId == m_pairs.Left ? Side.Right : Side.Left;

                            m_syncedRevs[sourceSide].Add(sourceRev, targetRev);

                            if (m_lastMigrated[sourceSide] < sourceRev)
                            {
                                m_lastMigrated[sourceSide] = sourceRev;
                            }

                            sourceSide = !sourceSide;

                            if (lastIncoming[sourceSide] < targetRev)
                            {
                                lastIncoming[sourceSide] = targetRev;
                            }
                        }
                    }
                }
                trn.Complete();
            }

            // Calculate the baselines
            m_baseline.Left = Math.Min(lastIncoming.Left, m_lastMigrated.Left);
            m_baseline.Right = Math.Min(lastIncoming.Right, m_lastMigrated.Right);
        }

        /// <summary>
        /// Gets paired item ids
        /// </summary>
        public Pair<string> ItemIds { get { return m_itemIds; } }

        /// <summary>
        /// Returns minimum between the last revision migrated to the opposite side and the last
        /// revision created by migration from the opposite side.
        /// </summary>
        public Pair<int> Baseline { get { return m_baseline; } }

        /// <summary>
        /// For each side returns number of the most recent revision migrated to the opposite side.
        /// </summary>
        public Pair<int> LastMigrated { get { return m_lastMigrated; } }

        /// <summary>
        /// Returns shared data object.
        /// </summary>
        public SyncProcess SharedData { get { return m_sharedData; } }

        /// <summary>
        /// Returns map of synced revisions.
        /// </summary>
        public Pair<Dictionary<int, int>> SyncedRevisions { get { return m_syncedRevs; } }

        /// <summary>
        /// Determines whether given revision was created as a result of migration from the other side.
        /// </summary>
        /// <param name="revisionSide">Side the revision belongs to</param>
        /// <param name="rev">Revision number</param>
        /// <returns>True if revision was migrated</returns>
        public bool IsSyncedRevision(
            Side revisionSide,
            int rev)
        {
            return m_syncedRevs[!revisionSide].ContainsKey(rev);
        }

        /// <summary>
        /// Updates mapping after a revision was migrated.
        /// </summary>
        /// <param name="targetSide">Side which was updated</param>
        /// <param name="sourceRev">Source revision</param>
        /// <param name="target">Migrated revision</param>
        public void Update(
            Side targetSide,
            int sourceRev,
            Watermark target)
        {
            if (m_pairs[targetSide] == -1)
            {
                // This should happen only when migrating first revision, which always goes from
                // left to right.
                Debug.Assert(targetSide == Side.Right, "Invalid target side!");
                PairItems(sourceRev, target);
            }
            else if (!m_sharedData.IsFlat)
            {
                AddRevision(targetSide, sourceRev, target.Revision);
            }
        }

        /// <summary>
        /// Pairs two work items.
        /// </summary>
        /// <param name="sourceRev">Source revision</param>
        /// <param name="target">Target work item/revision</param>
        private void PairItems(
            int sourceRev,
            Watermark target)
        {
            int targetRev;

            if (m_sharedData.IsFlat)
            {
                sourceRev = -1;
                targetRev = -1;
            }
            else
            {
                targetRev = target.Revision;
            }

            using (MigrationSqlTransaction trn = (MigrationSqlTransaction)DataAccessManager.Current.StartTransaction())
            {
                using (SqlCommand cmd = trn.CreateCommand())
                {
                    cmd.CommandText = "prc_iiPairWorkItems";
                    cmd.CommandType = CommandType.StoredProcedure;

                    cmd.Parameters.Add("@SessionId", SqlDbType.Int).Value = m_sessionId;
                    cmd.Parameters.Add("@SrcItemId", SqlDbType.Int).Value = m_ids.Left;
                    cmd.Parameters.Add("@SrcRev", SqlDbType.Int).Value = sourceRev;
                    cmd.Parameters.Add("@TgtSourceId", SqlDbType.Int).Value = m_sourceId.Right;
                    cmd.Parameters.Add("@TgtItemPublicId", SqlDbType.NVarChar, SqlData.WorkItemIdLength).Value = target.Id;
                    cmd.Parameters.Add("@TgtRev", SqlDbType.Int).Value = targetRev;

                    using (SqlDataReader reader = cmd.ExecuteReader())
                    {
                        reader.Read();
                        m_pairs.Right = (int)reader["SourcePairId"];
                        m_pairs.Left = (int)reader["TargetPairId"];
                    }
                }

                trn.Complete();
            }
        }

        /// <summary>
        /// Adds new revision to the mapping.
        /// </summary>
        /// <param name="targetSide">Side which was updated</param>
        /// <param name="sourceRev">Source revision</param>
        /// <param name="targetRev">Target revision</param>
        private void AddRevision(
            Side targetSide,
            int sourceRev,
            int targetRev)
        {
            using (MigrationSqlTransaction trn = (MigrationSqlTransaction)DataAccessManager.Current.StartTransaction())
            {
                using (SqlCommand cmd = trn.CreateCommand())
                {
                    cmd.CommandText = "INSERT INTO SyncedRevisions(PairId, SourceRev, TargetRev) VALUES (@PairId, @SourceRev, @TargetRev)";
                    cmd.CommandType = CommandType.Text;
                    cmd.Parameters.Add("@PairId", SqlDbType.Int).Value = m_pairs[targetSide];
                    cmd.Parameters.Add("@SourceRev", SqlDbType.Int).Value = sourceRev;
                    cmd.Parameters.Add("@TargetRev", SqlDbType.Int).Value = targetRev;

                    cmd.ExecuteNonQuery();
                }
                trn.Complete();
            }
        }
    }
}
